#include "Orthographic.h"
#include "../core/geometry.h"
#include "../core/sampling.h"
#include "../core/film.h"

OrthographicCamera::OrthographicCamera(const Transform &w2c, float cNear, float cFar, 
									   float sOpen, float sClose, float lensr, float focald, Reference<Film> fm):
										ProjectiveCamera(w2c, Orthographic(cNear, cFar), 
											cNear, cFar, sOpen, sClose, lensr, focald, fm) {
	dxCamera = rasterToCamera(Vector3(1,0,0));
	dyCamera = rasterToCamera(Vector3(0,1,0));
}

float OrthographicCamera::GenerateRay(const Point2 &imageSample, const Point2 &lensSample, float timeSample, Ray *ray) const {
	
	Point3 Pras(imageSample.x, imageSample.y, 0);
	Point3 Pcamera;
	rasterToCamera(Pras, &Pcamera);

	// In orthographic camera, rays always point to +z direction
	Vector3 dir = Vector3(0,0,1);
	*ray = Ray(Pcamera, dir);

	// Modify ray for depth of field
	// Since we start from the clipNear plane instead of the camera position
	// The implementation is slightly different to that of PerspectiveCamera
	if(lensRadius > 0.) {

		// Sample point on lens
		Point2 lens = ConcentricSamplingDisk(lensSample) * lensRadius;

		// Get focus point in focus plane
		float ft = (focalDistance - clipNear); // dir.z = 1, we can save a division
		Point3 Pfocus = (*ray)(ft);

		// Perturb the origin of the ray
		ray->o.x += lens.x * (focalDistance - clipNear) / focalDistance;
		ray->o.y += lens.y * (focalDistance - clipNear) / focalDistance;

		// Modify the direction of the ray
		ray->d = Normalize(Pfocus - ray->o);
	}

	ray->time = Lerp(timeSample, shutterOpen, shutterClose);

	// since we have translate clipNear to 0 in camera space
	ray->mint = 0;	// default to EPSILON(6)
	ray->maxt = clipFar - clipNear;

	cameraToWorld(*ray, ray);

	return 1.f;
}
float OrthographicCamera::GenerateDifferentialRay(const Point2 &imageSample, const Point2 &lensSample, float timeSample, RayDifferential *ray) const {
	
	Point3 Pras(imageSample.x, imageSample.y, 0);
	Point3 Pcamera;
	rasterToCamera(Pras, &Pcamera);
	
	Vector3 dir = Vector3(0,0,1);
	*ray = RayDifferential(Pcamera, dir);

	// Modify ray for depth of field
	// Since we start from the clipNear plane instead of the camera position
	// The implementation is slightly different to that of PerspectiveCamera
	if(lensRadius > 0.) {

		// Sample point on lens
		Point2 lens = ConcentricSamplingDisk(lensSample) * lensRadius;

		// Get focus point in focus plane
		float ft = (focalDistance - clipNear);	// dir.z = 1, we can save a division
		Point3 Pfocus = (*ray)(ft);

		// Perturb the origin of the ray
		ray->o.x += lens.x * (focalDistance - clipNear) / focalDistance;
		ray->o.y += lens.y * (focalDistance - clipNear) / focalDistance;

		// Modify the direction of the ray
		ray->d = Normalize(Pfocus - ray->o);
	}

	ray->time = Lerp(timeSample, shutterOpen, shutterClose);

	// since we have translate clipNear to 0 in camera space
	ray->mint = 0;	// default to EPSILON(6)
	ray->maxt = clipFar - clipNear;

	ray->rxDirection = ray->ryDirection = ray->d;
	ray->rxOrigin = ray->o + dxCamera;
	ray->ryOrigin = ray->o + dyCamera;

	ray->hasDifferentials = true;

	cameraToWorld(*ray, ray);

	return 1.f;
}

bool OrthographicCamera::NeedLensSample() const {
	return lensRadius > 0.;
}

std::string OrthographicCamera::ToString() const {
	std::ostringstream oss;

	oss << "OrthographicCamera[" << std::endl
		<< "  aspect = " << (float)film->GetSize().x/film->GetSize().y<< "," << std::endl
		<< "  nearClip = " << clipNear << "," << std::endl
		<< "  farClip = " << clipFar << "," << std::endl
		<< "  shutterOpen = " << shutterOpen << "," << std::endl
		<< "  shutterClose = " << shutterClose << "," << std::endl
		<< "  lensRadius = " << lensRadius << "," << std::endl
		<< "  focalDistance = " << focalDistance << "," << std::endl
		<< "  cameraToWorld = " << (cameraToWorld.ToString()) << "," << std::endl
		<< "  cameraToScreen = " << (cameraToScreen.ToString()) << "," << std::endl
		<< "  rasterToCamera = " << (rasterToCamera.ToString()) << std::endl
		<< "]";
	
	return oss.str();
}